package com.ktjiaoyu.server.config.security;

import com.ktjiaoyu.server.pojo.Admin;
import com.ktjiaoyu.server.service.IAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private RestAuthorizationEntryPoint restAuthorizationEntryPoint;

    @Resource
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;

    @Autowired
    private IAdminService adminService;

    @Resource
    private CustomFilter customFilter;
    @Resource
    private CustomUrlDecisionManager customUrlDecisionManager;

    //SpringSecurity主体的登录逻辑是通过UserDetailService里的loadUserByUsername()方法去实现的
    //这里我们使用@Override重写WebSecurityConfigurerAdapter类里定义的userDetailsService()方法
    //WebSecurityConfigurerAdapter类里定义的userDetailsService()方法是对UserDetailsService接口的实现
    // 重写WebSecurityConfigurerAdapter类中的userDetailsService()方法UserDetailsService接口里的参数名(其实也可以看成重写UserDetailsService中的loadUserByUserName()方法)
    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        return username -> { // 这里username是前端请求过来的用户名
            // 实现通过用户名查询用户详情对象信息
            Admin admin = adminService.getAdminByUserName(username);
            if (null != admin) {
                // 通过用户ID查询用户所拥有的角色列表，以及每个角色所拥有的权限列表
                admin.setRoles(adminService.findRolesAndMenusByAdminId(admin.getId()));
                return admin;
            }
            throw new UsernameNotFoundException("用户名密码不正确");
        };
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 下面配置后，Spring Security就会走上面我们自己重写的UserDetailService
        // 而密码匹配则是通过passwordEncoder()方法完成的。
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    // 在AdminServiceImpl中执行loadUserByUsername()方法时，就会执行自定义的实现的。
    }

    // 配置放行请求路径，如登录的请求路径 /login，以及一些静态资源的请求路径
    // 还包括Swagger的访问路径 （不被拦截）。
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                "/captcha",
                "/login",
                "/logout",
                "/css/**",
                "/js/**",
                "/index.html",
                "favicon.ico",
                "/doc.html",
                "/webjars/**",
                "/swagger-resources/**",
                "/v2/api-docs/**"
        );
    }

    //创建密码匹配器的bean实例对象
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 进行Spring Security的完整配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 授权认证
        // 设置除了登录页面，其他所有请求都必须被登录认证后才能进行访问。
        http.authorizeRequests()
                // 允许登录、退出进行访问   premitAll允许任何人都可以访问
                .antMatchers("/login", "/logout").permitAll()
                // 除了上面/login和/logout，其他所有请求都要求认证
                // anyRequest()为所有的请求， authenticated()表示登录认证后
                .anyRequest()
                .authenticated()
                // 添加动态权限的配置
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setAccessDecisionManager(customUrlDecisionManager);
                        o.setSecurityMetadataSource(customFilter);
                        return o;
                    }
                });
        ;

        // 使用JWT，不需要csrf，调用disable()方法，关闭csrf防火墙
        http.csrf()
                .disable()
                // 基于token，不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // 禁用缓存
        http.headers()
                .cacheControl();

        //添加拦截器
        // 添加jwt 登录授权
        http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        // 添加自定义未授权和未登录结果返回
        http.exceptionHandling()
                // 未登录
                .accessDeniedHandler(restfulAccessDeniedHandler)
                // 未授权
                .authenticationEntryPoint(restAuthorizationEntryPoint);
    }

    @Bean
    public JwtAuthencationTokenFilter jwtAuthencationTokenFilter() {
        return new JwtAuthencationTokenFilter();
    }


}
